package org.flexthinker.binding.ui.form.binding.support
{
    import flash.utils.Dictionary;

    import org.flexthinker.binding.bean.Binding;

    import org.flexthinker.binding.bean.Binding;
    import org.flexthinker.binding.bean.BindingGroup;
    import org.flexthinker.binding.form.FormModel;
    import org.flexthinker.binding.ui.form.binding.Binder;

    /**
     * Base class for FormBinding, responsible of creating <code>Bindings</code>
     * corresponding to each given <code>Binder</code>.
     *
     * @see org.flexthinker.binding.ui.form.binding.Binder
     */
    public class BaseFormBinding extends BindingGroup
    {
        /**
         * Map [formProperty,Binder] to ensure there is only 1 Binder per each property
         */
        protected var registeredBinders:Dictionary = new Dictionary(true);

        private var formModel:FormModel;

        private var _isBound:Boolean = false;

        public function BaseFormBinding(formModel:FormModel = null)
        {
            if ( formModel )
            {
                setFormModel(formModel);
            }
        }

        /**
         * Adds a new <code>Binder</code> to this <code>BaseFormBinding</code>.
         * <p>
         *     The <code>Binding</code> instance that corresponds to this <code>Binder</code>
         *     is not created until <code>bind()</code> method is called.
         *     </p>
         * @param binder
         *          A Binder instance. It could be a <code>FieldBinder</code>,
         *          <code>TextInputBinder</code>, or any other custom <code>Binder</code>.
         *
         * @see org.flexthinker.binding.ui.form.binding.support.FieldBinder
         */
        public function addFieldBinder( binder:Binder ):void
        {
            if ( !binder )
            {
                throw new ArgumentError("Cannot add a null binder. Please provide a non-null Binder");
            }
            if ( getFieldBinder(binder.getFormProperty()) != null )
            {
                throw new ArgumentError("The Binder is already added.");
            }

            registeredBinders[binder.getFormProperty()] = binder;

            if ( isBound )
            {
                var b:Binding = binder.getBinding( getFormModel() );
                if ( ! bindingExists(b) )
                {
                    addBinding( b );
                }
                b.bind();
            }
        }

        public function removeFieldBinder(binder:Binder):void
        {
            if (!binder )
            {
                throw new ArgumentError("Cannot remove a null binder.")
            }
            if ( getFieldBinder(binder.getFormProperty()) == null )
            {
                throw new ArgumentError("The Binder hasn't been added.Cannot remove it. Please add binders using addFieldBinder method");
            }

            if ( isBound )
            {
                // createBinding is returning an existing Binding, if one exists
                var b:Binding = binder.getBinding( getFormModel() );
                if ( bindingExists(b) )
                {
                    removeBinding(b);
                }
                b.unbind();
            }

            registeredBinders[binder.getFormProperty()] = null;

            delete registeredBinders[binder.getFormProperty()];
        }

        /**
         * Returns the <code>Binder</code> that corresponds to the given <code>formProperty</code>
         * @param formProperty
         */
        public function getFieldBinder(formProperty:String):Binder
        {
            return registeredBinders[formProperty] as Binder;
        }

        /**
         * Sets the source of Binding for all Binders.
         * A <code>FormModel</code> has to be set before calling <code>bind()</code> method.
         * @param formModel
         *          the binding source for all Binders
         *
         * @see org.flexthinker.binding.form.FormModel
         */
        public function setFormModel( formModel:FormModel ):void
        {
            if ( formModel == null )
            {
                throw new ArgumentError("Could not set a null form model. Please provide a non-null FormModel");
            }
            this.formModel = formModel;
        }

        public function getFormModel():FormModel
        {
            return formModel;
        }

        override public function bind():void
        {
            throwIfInvalidFormModel();
            createBindingsFromBinders();

            super.bind();

            _isBound = true;
        }

        override public function unbind():void
        {
            super.unbind();

            _isBound = false;
        }

        public function get isBound():Boolean
        {
            return _isBound;
        }

        // ------------------------
        //   internal methods
        // ------------------------

        protected function throwIfInvalidFormModel():void
        {
            if ( formModel == null )
            {
                throw new Error("Could not bind() without a FormModel. Provide first a valid FormModel, by calling setFormModel() method");
            }
        }

        private function createBindingsFromBinders():void
        {
            var b:Binding;
            for each ( var binder:Binder in registeredBinders )
            {
                b = binder.getBinding( getFormModel() );
                if ( ! bindingExists(b) )
                {
                    addBinding( b );
                }
            }
        }

        private function bindingExists(b:Binding):Boolean
        {
            return getBindings().indexOf(b) >= 0;
        }
    }
}
